昨日介紹完了 Promise
,今日要來介紹 ES7 的 await
、async
,但是進入主題之前,我們先來想想 Promise
為了改善非同步的處理,它做了什麼事情。
先從最根本的原因開始,JS 是屬於同步執行,但是遇到費時的操作,勢必得採取非同步處理,不然就會變成這樣:
如果 AJAX
是採取同步執行,那麼在取回資料前,接下來的程式都不會接著執行,除非 AJAX
完成,這也造成了整個程式堵塞住。
myAjax()
doOtherThing()
所以為了避免堵塞,將這些操作都使用非同步處理,但是問題來了,既然這些操作被拉到一旁執行,我們就需要知道它們何時執行完畢,因為我們可能會對它進行後續處理,像是這樣:
myAjax("one.com", function(val){
console.log(val);
})
我們用一個 callback function 來顯示 AJAX
收到的資料,目前為止看似沒問題,但是如果改成這樣呢?
我們需要顯示第 3 個來源的資料,但是下一個來源都需要前一個來源的值:
myAjax("one.com", function(val){
myAjax("two.com", val, function(val2){
myAjax("three.com", val2, function(val3){
console.log(val3);
})
})
})
也就是我需要先取得 val
,再用它來取得 val2
,然後再用 val2
來取得 val3
,這樣不斷嵌套下去,更別提每個 callback function 裡面滿滿的邏輯運算與 error 處理,程式碼變成逆時針倒 90 度的深 V,這是 ES5 以前所有開發者的噩夢。
直到 Promise
的出現,簡潔乾淨的寫法,配上一目瞭然的 then
與 catch
,讓 callback function 處理起來優雅多了XD
myAjax("one.com")
.then(val => myAjax("two.com", val))
.then(val2 => myAjax("three.com", val2))
.then(val3 => { console.log(val3); })
至於為什麼可以使用 then
與 catch
來拯救世界呢?
因為 Promise
的機制,它必須要給使用者有一個「回應」,而這個「回應」就只有兩種可能
resolve
reject
所以 then
負責接收 resolve
;catch
負責處理 reject
。
而且也不是一定要等前一個 Promise
回應後才能繼續下去,只要不是範例中需要前一個來源回應後,才能接下去執行的狀況,那更是可以使用 Promise.all
或 Promise.race
來針對不同情境,提供更方便快速的操作。
若是以上有些地方不太清楚,可以回顧
Day5 單執行緒&非同步發生的血案
Day21 AJAX(1): 科普 & XHR
Day23 Promise 詳解(1/2)
Day24 Promise 詳解(2/2)
我們的系列文可是從基礎打起又不失實用
我的用心,您看的見
講了那麼多,趕緊進入業配主題: await
、async
。
既然 Promise
那麼方便,為何還需要它們兩個呢,而且它們又是什麼東西...?
先講結論:
await
、async
也是基於 Promise
的應用,所以不是不相干的東西。我們都知道,非同步執行的程式碼,不管 callback function 多快執行,都無法在下一行的程式碼之前執行,像是這樣:
以下範例博君一笑,請勿認真 ^.<
setTimeout(() => { console.log("我的手速練到極致") }, 0);
console.log("我爸是連X");
請問這樣印出的順序是什麼?
如果有好好複習以前的文章,應該不會答錯了。
答案是
我爸是連X
我的手速練到極致
如果想要讓 我爸是連X
在 我的手速練到極致
後面才印出,就要改成這樣,
setTimeout(() => {
console.log("我的手速練到極致");
console.log("我爸是連X");
}, 0);
讓它在非同步操作結束後才執行,也就是達到我們原本預期的執行順序。
以上狀況常常發生,我們總是要將程式碼改寫為非同步執行的邏輯。
像是最簡單的取得資料:
getData()
.then(val => {
console.log(val);
});
使用 await
能夠解決這個問題。
await
能夠在 Promise
回應狀態前,停止往下執行,所以上方的範例可以寫成這樣:
async function outer() {
var val = await getData();
console.log(val);
}
outer();
加了 await
的 getData
,在回應前,function outer
中的程式將不會往下執行,所以我們的 console.log(val)
也不需要再弄一個 then
,然後在 callback function 中才能取得資料印出了,好像又那麼優雅了一點XD
還是不清楚嗎,我們再舉回一開始的多站點例子。
此時我們可以使用更簡潔的方法來撰寫這個範例
myAjax("one.com")
.then(val => myAjax("two.com", val))
.then(val2 => myAjax("three.com", val2))
.then(val3 => { console.log(val3); })
可以改成
async function outer() {
var val = await myAjax("one.com");
var val2 = await myAjax("two.com", val);
var val3 = await myAjax("three.com", val2);
console.log(val3);
}
outer();
甚至寫得更簡短
(async () => {
var val2 = await myAjax("two.com", await myAjax("one.com"));
console.log(await myAjax("three.com", val2));
})()
await
:
非同步
處理更加細膩。await
將會回傳接在它後面的 Promise
狀態值(resolve
)。await
在收到 Promise
回應前,會暫停執行後續的程式。await
特性的第 3 點是個大麻煩,如果 Promise
狀態回傳錯誤(reject
),將導致後面的程式永遠卡住,不會再執行...
所以需要搭配 async
,也就是寫在上方範例中 function 前面的 async
。
async
會像 Promise
一樣回傳狀態(成功/失敗),所以能用 then
、catch
來處理。
所以說的更精準一點,await
暫停執行的是包住它、帶有 async
的 function 之內容。
所以我們可以在發生錯誤時,將錯誤拋出,程式碼可以寫成這樣,就和 Promise
的用法一樣,使用 catch
來處理例外/錯誤狀況。
async function outer() {
var val = await myAjax("one.com");
var val2 = await myAjax("two.com", val);
var val3 = await myAjax("three.com", val2);
console.log(val3);
}
outer()
.then(...)
.catch(error => { console.log(error); })
或是用傳統的 try catch
來處理錯誤:
async function outer() {
try {
var val = await myAjax("one.com");
// 以下省略...
}
catch (error) {
console.log(error);
}
}
outer()
而 async function
既然像 Promise
,它的狀態除了 reject
還會有 resolve
,而 resolve
的值就是函數自己的回傳值。
var outer = async () => {
var val = await myAjax("one.com");
...
return 123;
}
outer()
.then(val => {
console.log(val); // 123
})
看完是不是覺得 Promise
如果沒有搭配 await
、async
好像少了那麼一點優雅呢XD
今日的分享就到這,我們明天見